Ahora que ya conocemos la traducción objeto-array-relacional, necesitamos una librería que nos permita trabajar cómodamente con el motor de persistencia para buscar, crear, modificar y eliminar objetos. A continuación se describen brevemente los procesos y métodos del API de Object Freezer-Relational, que ha sido ampliada con varios métodos, ya que el diseño original solamente contempla el almacenamiento (store()) y la obtención (fetch()), debido a que otras operaciones pueden ser realizadas directamente sobre la base de datos. Para más información, consultar la documentación en línea del código fuente.
Para alta o modificación de registros existe un mismo método, store(). Este método congela los objetos, y si no tienen un ID único se lo asigna. Después, el array se pasa al motor de persistencia y éste, antes de almacenar el objeto, elimina su homónimo de la base de datos.
Como el resto de las funcionalidades, el almacenamiento está repartido entre la clase RelationalStorage y la implementación concreta. En la primera, se obtiene el objeto congelado y se recorren sus objetos y propiedades (analizando su tipo y serializando los arrays) y se envían al motor de persistencia para que realice las consultas SQL correspondientes.
Aunque pensar en almacenar objetos parece sencillo y exento de errores, existen varios retos a la hora de manejar información anidada y relacionada. Veamos cuáles son y las posibles soluciones:
Uno de los tipos básicos de PHP es el tipo recurso. Las variables de este tipo representan entidades externas al flujo del script y las áreas de memoria reservadas para éste. Los recursos son habitualmente manejados por las extensiones de PHP (escritas en C) y su información interna no es visible para los scripts. Un recurso puede ser una conexión a base de datos, un socket o un fichero en disco. Por todo lo anterior, Object_Freezer no puede almacenar variables de este tipo, no ya por las limitaciones del lenguaje sino por la naturaleza de los recursos, que suelen representar estados del compilador o sistema operativo en un momento concreto.
Si se desea almacenar conexiones a bases de datos, imágenes GD u otro tipo de recursos, existe una solución: crear clases-envoltura, que mantengan la información necesaria para crear un recurso con los parámetros deseados, y que implementen los métodos mágicos __sleep() y __wakeup() para restaurar los recursos cuando sean congelados y descongelados. Por ejemplo:
Class ConexionMysql {
//Datos necesarios para realizar la conexión
private $servidor;
private $usuario;
private $clave;
private $bd;
private $puerto;
//Conexión "real" a la BD
private $recurso;
function __construct($servidor="localhost", $usuario=, $clave, $bd, $puerto=3306) {
$this->servidor = $servidor;
$this->usuario = $usuario;
$this->clave = $clave;
$this->bd = $bd;
$this->puerto = $puerto;
$this->crearRecurso();
}
//Este método mágico es llamado al de-serializar un objeto
public function __wakeup() {
$this->crearRecurso();
}
public function crearRecurso() {
$this->recurso = mysql_connect(
$this->servidor,
$this->usuario,
$this->clave,
$this->puerto);
mysql_select_db($this->bd, $this->recurso);
}
}
La lectura de registros se puede realizar conociendo su ID —a través del método Storage::fetch()— o a través de los métodos de búsqueda. Ya que estos emplean el primero, es conveniente explicar cómo funciona doFetch(), la implementación para MySQL de fetch(): básicamente, se trata de obtener los datos del objeto (clase, isRoot, etc) desde la tabla objects, así como sus propiedades desde la tabla properties, y procesar recursivamente éstas para obtener un array bajo la forma de frozen object. Una vez obtenido, Object_Freezer::thaw() se encarga de convertirlo en un objeto.
Los métodos de búsqueda son los siguientes:
Este método obtiene todas las instancias de una determinada clase, almacenadas en un array. Si la opción de Lazy Load está activada, se devolverán sólamente los ID. En caso contrario, las instancias son cargadas también.
Este método provee una búsqueda más refinada sobre objetos. Permite obtener los objetos en base a una propiedad, que puede compararse (igual, mayor que, menor que, etcétera) con un determinado valor. Además, es posible ordenar los resultados según la propiedad especificada, así como limitar el número de resultados. Es importante destacar que este método devuelve objetos de cualquier clase que cumplan las condiciones especificadas, a no ser que se especifique una clase concreta como parámetro.
Eliminar un objeto no es tan sencillo como eliminar sus filas correspondiente en las tablas objects y properties. Ya que Object_Freezer-Relational implementa un almacenamiento objeto-relacional, hay que cumplir las obligaciones que nos impone la integridad referencial.
Pongamos por ejemplo que tenemos dos objetos A y B, y ambos tienen la propiedad X con el valor Y, que es un objeto. Si eliminamos A, no podemos eliminar su propiedad X (de valor Y), ya que estaríamos alterando el objeto B.
Por una parte, este problema, ya existente en el manejo de objetos en memoria que hacen los lenguajes de programación, se resuelve manteniendo los objetos hijo y con los llamados "recolectores de basura", que eliminan los objetos no referenciados.
Por la otra, en la las bases de datos relacionales el ejemplo planteado es un padre-hijo (donde, al contrario de la POO, Y es el padre y A y B los hijos), y eliminar un hijo no implica eliminar un padre, aunque la eliminación de los padres sí implique eliminar los hijos, al menos si en las relaciones se especifica una clásusula de eliminación en cascada.
Por ello, nuestra librería ha intentado ser fiel al modelo orientado a objetos pero manteniendo las restricciones que nos impone un almacenamiento relacional. Esto quiere decir que, por defecto, cuando el usuario solicita eliminar A, Object_Freezer-Relational eliminará el objeto anidado Y sólo si nadie lo referencia. A este comportamiento le llamaremos "eliminación en modo Auto". No obstante, este comportamiento se puede cambiar a través del tercer parámetro de RelationalStorage::remove(), especificando "borrado total", en cuyo caso se eliminarán todos los objetos anidados estén o no referenciados por otros; o bien con "preservar hijos", que no eliminará ningún objeto anidado.
Es obvio que la eliminación total puede producir graves fallos de integridad referencial, aunque es utilizado durante la modificación de objetos:
Uno podría pensar que es un error que Y, que en cierto modo pertenecía a B, ahora tenga un valor diferente al que tenía cuando fue persistido. Empero, ésta es una de las diferencias clave entre el enfoque orientado a documentos que sigue Object_Freezer-CouchDB y el enfoque relacional: en las bases de datos tradicionales es evidente que se puede modificar un registro padre independientemente de las filas que lo referencien, mientras que el enfoque orientado a documentos produce una independencia de objetos anidados: el Y de A no es el mismo que el Y de B, por lo que puede ser modificado para uno y no para otro, con la inseguridad de integridad y la gran cantidad de información repetida que ello produce.
El último modo de eliminación que nos falta explicar es el "preservar hijos". Si queremos eliminar A, se eliminará la fila correspondiente al objeto A, sus propiedades y sus referencias, pero nunca los objetos referenciados, aunque ningún otro objeto los use. Utilizar este comportamiento no es recomendable por dos razones: